﻿using OpenCvSharp;
using OpenCvSharp.WpfExtensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;

namespace OpenCvSharpExtension
{
    /// <summary>
    /// 相机组件
    /// </summary>
    public class CameraComponent : System.Windows.DependencyObject, IDisposable
    {
        /// <param name="cameraIndex">相机索引</param>
        /// <param name="frameWidth">相机画面最大宽度</param>
        /// <param name="frameHeight">相机画面最大高度</param>
        public CameraComponent(int cameraIndex, int frameWidth = 1920, int frameHeight = 1080)
        {
            this.CameraIndex = cameraIndex;
            this.frameMat = new Mat();
            this.Framerate = 24;
            thread = new Thread(captureWhile);
            thread.Start();
            this.capture = new VideoCapture(cameraIndex, VideoCaptureAPIs.DSHOW, new VideoCapturePara(VideoAccelerationType.Any, cameraIndex));
            /*
             CV_FOURCC('P', 'I', 'M', '1') = MPEG-1 codec
            CV_FOURCC('M', 'J', 'P', 'G') = motion-jpeg codec
            CV_FOURCC('M', 'P', '4', '2') = MPEG-4.2 codec 
            CV_FOURCC('D', 'I', 'V', '3') = MPEG-4.3 codec 
            CV_FOURCC('D', 'I', 'V', 'X') = MPEG-4 codec 
            CV_FOURCC('U', '2', '6', '3') = H263 codec 
            CV_FOURCC('I', '2', '6', '3') = H263I codec 
            CV_FOURCC('F', 'L', 'V', '1') = FLV1 codec 
            CV_FOURCC('A', 'V', 'C', '1') =  H264 codec
             
             */
            capture.Set(VideoCaptureProperties.FourCC, OpenCvSharp.VideoWriter.FourCC("MJPG"));//高清摄像头用MJPG才不卡
            capture.Set(VideoCaptureProperties.FrameWidth, frameWidth);
            capture.Set(VideoCaptureProperties.FrameHeight, frameHeight);
        }
        private readonly Thread thread;
        private readonly VideoCapture capture;

        private DateTime lastTime;
        private TimeSpan interval;

        /// <summary>
        /// 可用于在捕获画面的过程中动态调整相机属性
        /// </summary>
        public VideoCapture VideoCapture { get { return capture; } }

        /// <summary>
        /// 图像翻转，<see cref="FlipMode.X"/>沿X轴翻转，<see cref="FlipMode.Y"/>沿Y轴翻转。
        /// </summary>
        public FlipMode? FlipMode { get; set; }

        /// <summary>
        /// 相机索引
        /// </summary>
        public int CameraIndex { get; private set; }

        private int _Framerate = 24;

        /// <summary>
        /// 帧率
        /// </summary>
        public int Framerate
        {
            get { return _Framerate; }
            set
            {
                _Framerate = value;
                interval = oneSec / value;

            }
        }




        /// <summary>
        /// fps
        /// </summary>
        public int FPS { get; private set; } = 0;
        /// <summary>
        /// 渲染FPS信息
        /// </summary>
        public bool DrawFPS { get; set; } = false;


        /// <summary>
        /// 获取当前位图
        /// </summary>
        public WriteableBitmap Bitmap
        {
            get => bitmap;
        }
        /// <summary>
        /// 原始帧图像（OpenCV格式）
        /// </summary>
        public Mat FrameMat
        {
            get => frameMat;
        }

        /// <summary>
        /// 暂停
        /// </summary>
        public bool IsPaused { get; set; }

        private bool isclose = false;

        private WriteableBitmap bitmap;
        private WriteableBitmap take = null;
        private bool istake = false;
        private bool taskIsReturn = false;//任务是否已退出

        /// <summary>
        /// 打开摄像头
        /// </summary>
        /// <returns></returns>
        public bool Open()
        {

            var result = capture.Open(this.CameraIndex, VideoCaptureAPIs.DSHOW);

            return result;
        }
        /// <summary>
        /// 关闭摄像头
        /// </summary>
        public void Close()
        {
            isclose = true;
            capture.Release();
            this.bitmap = null;
            this.OnBitmapChanged();
        }


        /// <summary>
        /// 捕获下一帧画面
        /// </summary>
        /// <returns></returns>
        public async Task<WriteableBitmap> Capture()
        {
            istake = true;
            while (istake)
            {
                await Task.Delay(10);
            }
            return take;
        }

        private void captureWhile()
        {
            while (!isdisposed)
            {
                if (isclose)
                {
                    Thread.Sleep(100);
                    continue;
                }
                render();
            }
            taskIsReturn = true;//任务已退出
        }
        private static TimeSpan oneSec = TimeSpan.FromSeconds(1);
        private Mat frameMat;
        private void render()
        {
            if (capture == null || capture.IsDisposed)
            {
                return;
            }
            if (IsPaused)
            {
                return;
            }
            var now = DateTime.Now;
            var t = now - lastTime;
            if (t < interval)
            {
                return;
            }
            this.FPS = Convert.ToInt32(oneSec / t);
            lastTime = now;

            var grabbed = capture.Read(frameMat);
            if (!grabbed || frameMat.Empty())
            {
                take = null;
                istake = false;
                this.bitmap = null;
                this.OnBitmapChanged();
                return;
            }
            this.OnBeforeFrame();
            if (FlipMode.HasValue)
            {
                Cv2.Flip(frameMat, frameMat, this.FlipMode.Value);
            }
            if (DrawFPS)
            {
                Cv2.PutText(frameMat, $"{this.FPS}", new Point(20, 30), HersheyFonts.HersheyComplex, 1, Scalar.Green, 1);
            }

            if (istake)
            {
                take = this.Dispatcher.Invoke(() => WriteableBitmapConverter.ToWriteableBitmap(frameMat));
                istake = false;
            }

            if (bitmap == null)
            {//当前没有图像或图像尺寸不一致
                this.bitmap = this.Dispatcher.Invoke(() => WriteableBitmapConverter.ToWriteableBitmap(frameMat));
                this.OnBitmapChanged();
            }
            else
            {
                this.Dispatcher.BeginInvoke(new Action(delegate
                {
                    if (this.bitmap != null)
                    {
                        WriteableBitmapConverter.ToWriteableBitmap(frameMat, this.bitmap);
                    }
                }));

            }
            this.OnGotFrame();
        }

        private bool isdisposed = false;//已释放
        public void Dispose()
        {
            isdisposed = true;
            while (!taskIsReturn)
            {
                Thread.Sleep(20);
            }
            this.Close();
        }

        /// <summary>
        /// 在当前位图指针发生变化时触发（位图的实际内容变化时并不触发）
        /// </summary>
        public event EventHandler BitmapChanged;

        private void OnBitmapChanged()
        {
            try
            {
                if (this.BitmapChanged != null)
                {
                    this.Dispatcher.Invoke(delegate
                                   {
                                       this.BitmapChanged.Invoke(this, EventArgs.Empty);
                                   });
                }

            }
            catch (Exception)
            {
                throw;
            }

        }
        /// <summary>
        /// 获取到新的帧时触发
        /// </summary>
        public event EventHandler GotFrame;
        private void OnGotFrame()
        {
            try
            {
                this.GotFrame?.Invoke(this, EventArgs.Empty);
            }
            catch (Exception)
            {
                throw;
            }
        }
        /// <summary>
        /// 获取到新的帧时触发
        /// </summary>
        public event EventHandler BeforeFrame;
        private void OnBeforeFrame()
        {
            try
            {
                this.BeforeFrame?.Invoke(this, EventArgs.Empty);
            }
            catch (Exception)
            {
                throw;
            }
        }

    }
}
